"""
在这个文件中提供寻路服务:
[Get Request] http://localhost/route/from=119.1234,21.45678&to=122.8765,24.77821&is_primary_arc=yes
[Respond - ok] {"code":0, "description": "ok", "data": [[119.1,21.4], [119.2,21.5], [119.3,21.6], ...., [122.9, 24.8]]}
[Respond - failed] {"code":1, "description": "The dest is not reachable."}
伪代码：
r = RouteServe(LocalCache.instance(), from, to, is_primary_arc, opts)
error_code, point_list, error_describe = r.route()
respond4method_get({"code":error_code, "description": error_describe, "data": point_list})
"""
import math

from astar import AStar
from local_cache import LocalCache

EXTEND_LON = 3
EXTEND_LAT = 3

def rects_primary_arc_idcl(p1, p2):
    """
    参数p1(x1, y1), p2(x2, y2) 是经纬度。
    给定两个点，返回三个区域。一个不跨越国际日期变更线的区域，和两个跨越国际日期变更线的区域。且返回primary arc是否穿越国际日期变更线。
    返回值值有两个：
    1 返回区域列表[(不跨越国际日期变更线的区域0), (跨越国际日期变更线的区域1), (跨越国际日期变更线的区域2)]，区域格式是
      元组(left, bottom, width, height)，其中区域1是idcl西侧的，区域2是idcl东侧的
    2 返回最短路径primary arc是否穿越ICDL的标识，如穿越返回True否则返回False。
    注意当前版本的算法中我们对是否最短路径做了简化处理：将地图上x方向最短的线路视为最短线路，y暂不处理。在后续的版本中继续优化。
    见图示：test/png/primary_arc_cross_idcl.png, test/png/primary_arc_not_cross_idcl.png,
    """
    (x1, y1) = p1
    (x2, y2) = p2
    # 在地球上任意两个不在国际日期变更线上的点，都有两条线路，穿过日期变更线或者不穿过日期变更线。
    # 见图示：test/png/primary_arc_cross_idcl.png, test/png/primary_arc_not_cross_idcl.png,
    # A：假定在传参时x1 <= x2。（非常重要！下面所有描述都基于此）
    # 假设x的范围是-180~180，x=180/-180的线是国际日期变更线，x=0的线是本初子午线，
    # 不穿越国际日期变更线时的x距离=(x2-x1)，穿越国际日期变更线时的x距离=(x1-x2+360)。（P.S x1到y轴的距离是x1-(-180), x2到x=180的坐标
    # 轴边缘距离是180-x2,二者相加）
    # 判断'穿越国际日期变更线时的'x距离是否大于'不穿越国际日期变更线时的'x距离：
    # (x1-x2+360) > (x2-x1) => [2 * x1 - 2 * x2 + 360 > 0] => [x1 - x2 > -180] => [x2 - x1 < 180]
    # B：如果在传参时候x2 <= x1，则为[x1 - x2 < 180]
    # 所以当abs(x1 - x2) <= 180时穿越国际日期变更线时的线路更长。据此拓展，因为360度是在-180/+180度制加上180，二个360度制的x值和两个
    # 180/-180制的x值相减后没有差别，所以输入参数既可以是-180/+180度制也可以是360度制（下面代码要稍作改动）。
    if abs(x1 - x2) <= 180:
        print("primary arc不穿越国际日期变更线。")
        is_primary_arc_cross_idcl = False
    else:
        print("primary arc穿越了国际日期变更线。")
        is_primary_arc_cross_idcl = True

    r = []
    # 不穿越国际日期变更线的矩形范围
    # 矩形区域的左下坐标
    left, bottom = min(x1, x2), min(y1, y2)  # right, top = max(x1, x2), max(y1, y2)
    width, height = abs(x1 - x2), abs(y1 - y2)
    r.append((left, bottom, width, height))

    # 穿越国际日期变更线的矩形范围
    # 需要拆成两块: 在日期变更线东边的和西边的。两个矩形具有相同的top和bottom
    top, bottom = max(y1, y2), min(y1, y2)
    height = abs(y1 - y2)
    # 东边的，left是国际日期变更线。
    e_left, e_right = 0, min(x1, x2)  # e for east
    e_width = abs(e_right - e_left)
    # 西边的，right是国际日期变更线。
    w_left, w_right = max(x1, x2), 180   # P.S 如果坐标系原点在(-180,-90)且是360度制的，这里改成360即可。
    w_width = abs(w_right - w_left)
    r.append((w_left, bottom, w_width, height))
    r.append((e_left, bottom, e_width, height))
    return r, is_primary_arc_cross_idcl


class RouteServe(AStar):
    def __init__(self, local_cache, point_from, point_to, is_primary_arc):
        self.__extend_and_align(point_from, point_to, is_primary_arc)
        grid = self.__prepare_data()
        AStar.__init__(self, size_cx, size_cy, grid, start, end)

    def __extend_and_align(self, point_from, point_to, is_primary_arc):
        """
        1要适量拓展起点和终点附近的空间，这样直线路径走不通的时候可以饶一下路。
        2要对齐到0.01度，这是目前支持的颗粒度。
        有时即便拓展了也会发生寻路失败，例如台湾岛是南北长中间窄的大岛，如果在中部东、西海岸设置起点和终点，则导航会因为台岛遮挡而失效。
        这时候就需要大幅度的拓展，以起点和终点作为对角线的矩形，向南北方向大范围拓展。见test/png/expand_lon_lat.png
        """
        x1, y1 = point_from
        x2, y2 = point_to
        rects, is_primary_arc_cross_idcl = rects_primary_arc_idcl(point_from, point_to)
        if is_primary_arc:
            if is_primary_arc_cross_idcl:
                areas = [rects[1], rects[2]]  # 或者写成areas = rects[1:]
            else:
                areas = [rects[0]]
        else:
            if is_primary_arc_cross_idcl:
                areas = [rects[0]]
            else:
                areas = [rects[1], rects[2]]
        # areas: [元组(left, bottom, width, height)]
        # 取整，然后拓展范围（见函数说明）。
        print("rects:", areas)
        if len(areas) == 1:   # not cross IDCL
            (left, bottom, width, height) = areas[0]
            left = round(left - 0.099, 1)
            left -= EXTEND_LON
            bottom -= EXTEND_LAT
            if left < -180:
                left += 360
            if bottom < -90:
                bottom = -90
            width += EXTEND_LON * 2
            height += EXTEND_LAT * 2

            # 得到寻路网格的大小
            size_cx, size_cy = width, height
            # (left,bottom)的经纬度对应的是网格图上的(0,0), left和bottom沿着x,y轴方向每加0.01度网格坐标加一。
            # 计算x1, y1, x2, y2在哪个网格中：
            start_x, start_y = round((x1-left)/0.01), round((y1-bottom)/0.01)
            end_x, end_y = round((x2-left)/0.01), round((y2-bottom)/0.01)
        else:  # IDCL Crossed
            # 取idcl东侧西侧的区域，西侧减,东侧加
            (left, bottom, width, height) = areas[0]  # IDCL西侧(平面坐标系靠近右侧边缘的部分)
            left = round(left - 0.099, 1)
            left -= EXTEND_LON
            bottom -= EXTEND_LAT
            if left < -180:
                left += 360
            if bottom < -90:
                bottom = -90
            (_, _, e_width, _) = areas[1]  # IDCL东侧（平面坐标系靠近左侧边缘的部分）
            size_cx = (width + e_width) + EXTEND_LON * 2
            size_cy = height + EXTEND_LAT * 2
            start_x, start_y = round((x1-left)/0.01), round((y1-bottom)/0.01)
            end_x, end_y = round((x2-left)/0.01), round((y2-bottom)/0.01)
        # 现在寻路的矩形区域已经确定下来了。
        self.grid_base = (left, bottom)
        self.size_cx = size_cx
        self.size_cy = size_cy
        self.start_coord_in_grid = (start_x, start_y)
        self.end_coord_in_grid = (end_x, end_y)
        return

    # def __prepare_data(self):
    #     cache = LocalCache(r"D:\source\python\navigation-plan-engine\postgres2osm\test\config\navieng.json")
    #     if cache.map_cache_ready:
    #         grid = cache.read_map_cache(lon, lat, h_cc, v_cc)  # 考虑参数问题。
    #     else:
    #         print(datetime.datetime.now(), "开始从hbase读取地图……")
    #         start_time = time.perf_counter()
    #         reader = GridReader(hbase_host, hbase_port, read_threads)
    #         grid = reader.read_grid_data(lon, lat, h_cc, v_cc)  # 是否可通行grid[i][j][0], 通行代价grid[i][j][1]
    #         print(datetime.datetime.now(), "地图数据读取完毕！耗时：", time.perf_counter() - start_time, "秒")
    #
    #     if cache.weather_cache_ready:
    #         grid_wea = cache.read_weather_cache(lon, lat, h_cc, v_cc)  # 考虑参数问题。
    #     else:
    #         weather = WeatherReader(weather_config, hbase_host, hbase_port)
    #         weather.dump_config()  # 测试，打印关键的配置信息。
    #         grid_wea = weather.read_weather(lon, lat, h_cc, v_cc)
    #     # grid i行j列 => weather的((lon * 100)+i)//25, ((lat * 100)+j)//25
    #     for i in range(0, h_cc):
    #         for j in range(0, v_cc):
    #             index_of_weather_cost_x = round((((lon + 180.0) * 100) + i) // 25)
    #             index_of_weather_cost_y = round((((lat + 90.0) * 100) + j) // 25)
    #             # print("寻路网格中的坐标是:", i, j, ",天气代价网格中的坐标是：", index_of_weather_cost_x, index_of_weather_cost_y)
    #             if grid[i][j][0] == 1:
    #                 # 目前只有天气权重，所以我们直接用天气权重的就好。
    #                 tmp1 = grid_wea[index_of_weather_cost_x][index_of_weather_cost_y]
    #                 if tmp1 != 255:
    #                     grid[i][j][1] = tmp1 // 10  # 通行代价的数值不能太大，否则目前的预测算法会出问题。这里要慢慢调整。
    #                 else:
    #                     grid[i][j][1] = tmp1
    #                 # print("代价值是：", grid[i][j][1])
    #     # -----------------读取数据结束-------------------

    def route(self):
        """
        point_from: (经度，纬度)，如(119.1234, 21.45678)。寻路的起始点
        point_to: 同上格式。 寻路的终点。
        is_primary_arc: 最短的大圆弧。两点之间至少有两条最近路线，如果这个参数为True，表示是较短的那一条，否则是较长的那一条。
        """
        self.finished = False
        self.running = True
        start_time = time.perf_counter()
        while not self.finished:
            self.step()
        end_time = time.perf_counter()
        print("寻路完成，耗时:{0}秒".format(end_time - start_time))